{{- (Globals).Put "enum_override_names" Map -}}

{{- /* ------------------------------------------------------------------ */ -}}
{{-                      define "OverrideEnumName"                           -}}
{{- /* Overrides the C++ name for a sem.Enum.                             */ -}}
{{- /* Arguments:                                                         */ -}}
{{- /* * 'Enum' the sem::Enum                                             */ -}}
{{- /* * 'Name' the new C++ name for enum                                 */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{-   $enum_override_names := (Globals).Get "enum_override_names" -}}
{{-   $enum_override_names.Put $.Enum $.Name -}}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "EnumName"                                -}}
{{- /* Prints the C++ name for the given sem.Enum argument.               */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{-   $enum_override_names := (Globals).Get "enum_override_names" -}}
{{-   $override := $enum_override_names.Get $ -}}
{{    if $override -}}
{{        $override  -}}
{{    else -}}
{{        PascalCase $.Name}}
{{-   end -}}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "EnumCase"                                -}}
{{- /* Prints the 'Enum::kEntry' name for the provided sem.EnumEntry      */ -}}
{{- /* argument.                                                          */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{-   Eval "EnumName" $.Enum}}::k{{PascalCase $.Name}}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "DeclareEnum"                             -}}
{{- /* Declares the 'enum class' for the provided sem.Enum argument.      */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := Eval "EnumName" $ -}}
enum class {{$enum}} : uint8_t {
    kUndefined,
{{-   range $entry := $.Entries }}
    k{{PascalCase $entry.Name}},{{if $entry.IsInternal}}  // Tint-internal enum entry - not parsed{{end}}
{{-   end }}
};

/// @param value the enum value
/// @returns the string for the given enum value
std::string_view ToString({{$enum}} value);

/// @param out the stream to write to
/// @param value the {{$enum}}
/// @returns @p out so calls can be chained
template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
auto& operator<<(STREAM& out, {{$enum}} value) {
    return out << ToString(value);
}

/// Parse{{$enum}} parses a {{$enum}} from a string.
/// @param str the string to parse
/// @returns the parsed enum, or {{$enum}}::kUndefined if the string could not be parsed.
{{$enum}} Parse{{$enum}}(std::string_view str);

constexpr const char* k{{$enum}}Strings[] = {
{{-   range $entry := $.Entries }}
{{-     if not $entry.IsInternal}}
    "{{$entry.Name}}",
{{-     end }}
{{-   end }}
};

{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                          define "ParseEnum"                              -}}
{{- /* Implements the 'ParseEnum' function for the provided sem.Enum      */ -}}
{{- /* argument.                                                          */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := Eval "EnumName" $ -}}
/// Parse{{$enum}} parses a {{$enum}} from a string.
/// @param str the string to parse
/// @returns the parsed enum, or {{$enum}}::kUndefined if the string could not be parsed.
{{$enum}} Parse{{$enum}}(std::string_view str) {
{{-   range $entry := $.PublicEntries }}
    if (str == "{{$entry.Name}}") {
        return {{template "EnumCase" $entry}};
    }
{{-   end }}
    return {{$enum}}::kUndefined;
}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "EnumOStream"                             -}}
{{- /* Implements the stream 'operator<<()' function to print the         */ -}}
{{- /* provided sem.Enum.                                                 */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := Eval "EnumName" $ -}}
std::string_view ToString({{$enum}} value) {
    switch (value) {
        case {{$enum}}::kUndefined:
            return "undefined";
{{-   range $entry := $.Entries }}
        case {{template "EnumCase" $entry}}:
            return "{{$entry.Name}}";
{{-   end }}
    }
    return "<unknown>";
}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                        define "TestParsePrintEnum"                       -}}
{{- /* Implements unit tests for parsing and printing the provided        */ -}}
{{- /* sem.Enum argument.                                                 */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := Eval "EnumName" $ -}}
namespace parse_print_tests {

struct Case {
    const char* string;
    {{$enum}} value;
};

inline std::ostream& operator<<(std::ostream& out, Case c) {
    return out << "'" << std::string(c.string) << "'";
}

static constexpr Case kValidCases[] = {
{{-   range $entry := $.PublicEntries }}
    {"{{$entry.Name}}", {{template "EnumCase" $entry}}},
{{-   end }}
};

static constexpr Case kInvalidCases[] = {
{{-   $exclude := $.NameSet -}}
{{-   range $entry := $.PublicEntries }}
    {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kUndefined},
    {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kUndefined},
    {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kUndefined},
{{-   end }}
};

using {{$enum}}ParseTest = testing::TestWithParam<Case>;

TEST_P({{$enum}}ParseTest, Parse) {
    const char* string = GetParam().string;
    {{$enum}} expect = GetParam().value;
    EXPECT_EQ(expect, Parse{{$enum}}(string));
}

INSTANTIATE_TEST_SUITE_P(ValidCases, {{$enum}}ParseTest, testing::ValuesIn(kValidCases));
INSTANTIATE_TEST_SUITE_P(InvalidCases, {{$enum}}ParseTest, testing::ValuesIn(kInvalidCases));

using {{$enum}}PrintTest = testing::TestWithParam<Case>;

TEST_P({{$enum}}PrintTest, Print) {
    {{$enum}} value = GetParam().value;
    const char* expect = GetParam().string;
    EXPECT_EQ(expect, tint::ToString(value));
}

INSTANTIATE_TEST_SUITE_P(ValidCases, {{$enum}}PrintTest, testing::ValuesIn(kValidCases));

}  // namespace parse_print_tests

{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                     define "BenchmarkParseEnum"                          -}}
{{- /* Implements a micro-benchmark for parsing the provided sem.Enum     */ -}}
{{- /* argument.                                                          */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := Eval "EnumName" $ -}}
void {{$enum}}Parser(::benchmark::State& state) {
    const char* kStrings[] = {
{{-   $exclude := $.NameSet -}}
{{-   range $entry := $.PublicEntries }}
        "{{Scramble $entry.Name $exclude}}",
        "{{Scramble $entry.Name $exclude}}",
        "{{Scramble $entry.Name $exclude}}",
        "{{$entry.Name}}",
        "{{Scramble $entry.Name $exclude}}",
        "{{Scramble $entry.Name $exclude}}",
        "{{Scramble $entry.Name $exclude}}",
{{-   end }}
    };
    for (auto _ : state) {
        for (auto* str : kStrings) {
            auto result = Parse{{$enum}}(str);
            benchmark::DoNotOptimize(result);
        }
    }
} // NOLINT(readability/fn_size)

BENCHMARK({{$enum}}Parser);
{{- end -}}
